#include "json/reader.h"
#include "json/value.h"
#include <chrono>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <functional>
#include <future>
#include <iostream>
#include<json/json.h>
#include <ostream>
#include <queue>
#include <string>
#include <thread>

#ifdef _WIN32
    #include<windows.h>
#else
    #include "unistd.h"
#endif

using std::cerr,std::endl,std::cout,std::ifstream,std::string,std::vector,std::queue,std::filesystem::path,std::thread,std::chrono::time_point,std::chrono::steady_clock,std::chrono::high_resolution_clock,std::chrono::system_clock,std::filesystem::exists;

#define AS_EQ(a,b){if((a)!=(b)){cerr<<"assert eq failed :"<<endl<<#a<<":"<<(a)<<endl<<#b<<":"<<(b)<<endl;exit(1);}}
#define AS_NE(a,b){if((a)==(b)){cerr<<"assert not eq failed :"<<endl<<#a<<":"<<(a)<<endl<<#b<<":"<<(b)<<endl;exit(1);}}
#define LOG(a){cout<<"[LOG]"<<(a)<<endl;}
#define AS_EM(a,b,m){if((a)!=(b)){cerr<<"assert eq failed :"<<endl<<#a<<":"<<(a)<<endl<<#b<<":"<<(b)<<endl<<"message:"<<(m)<<endl;exit(1);}}
#define AS_NM(a,b,m){if((a)==(b)){cerr<<"assert not eq failed :"<<endl<<#a<<":"<<(a)<<endl<<#b<<":"<<(b)<<endl<<"message:"<<(m)<<endl;exit(1);}}
#define printValue(v){cout<<#v<<":"<<(v)<<endl;}

#ifdef _WIN32
    bool isWindows = true;
#else
    bool isWindows=false;
#endif

#ifdef _WIN32
    unsigned long getCpuNums(){
        SYSTEM_INFO sysInfo;
        GetSystemInfo( &sysInfo );
        return sysInfo.dwNumberOfProcessors;
    }
#else 
    unsigned long getCpuNums(){
        return sysconf(_SC_NPROCESSORS_ONLN);
    }
#endif

std::filesystem::path getAbsolutePath(std::filesystem::path rootPath,std::filesystem::path filePath);
string getProtectPath(const std::filesystem::path &p);

struct Test{
    unsigned int id;
    std::filesystem::path in;
    std::filesystem::path ans;
    friend std::ostream& operator<<(std::ostream& os,Test &t){
        os<<"Test{"<<"in:"<<t.in<<",ans:"<<t.ans<<"} ";
        return os;
    }
};

struct ProgramRet{
    #ifdef _WIN32
        time_point<steady_clock> start;
        time_point<steady_clock> end;
    #else
    time_point<system_clock> start;
    time_point<system_clock> end;
    #endif
    
    path outPath;
    unsigned int id;
    friend std::ostream& operator<<(std::ostream&os,ProgramRet&ret){
        #ifdef _WIN32
            os<<"ProgramRet { "<<" outPath: "<<ret.outPath<<" ,id: "<<ret.id<<" }";
        #else
            os<<"ProgramRet { start: "<<ret.start<<" ,end: "<<ret.end<<" ,outPath: "<<ret.outPath<<" ,id: "<<ret.id<<" }";
        #endif
        return os;
    }
};

template<typename T>
T getValueOrPanic(T value);
int compareFiles(const path &path1, const path &path2) ;

struct Statu{
    bool isTLE = false;
    int cmp;
    int id;
    std::chrono::duration<long, std::ratio<1, 1000>> spendTime;

    friend std::ostream& operator<<(std::ostream &os,Statu &s){
        if(s.isTLE){
            os<<"tests\t"<<s.id<<"\tTLE";
        }else if(s.cmp==0){
            os<<"tests\t"<<s.id<<"\tAC\t"<<s.spendTime.count()<<"ms";
        }else{
            os<<"tests\t"<<s.id<<"\tWA line:"<<s.cmp<<"\t"<<s.spendTime.count()<<"ms";
        }
        return os;
    }
};

int main(int argc, char* argv[]) {
    LOG("Zengtudor OI test tools . loading files .. please add a json file in arg");
    printValue(argv[0])
    AS_EQ(argc,2)
    std::filesystem::path jsonPath(argv[1]);
    if (!jsonPath.is_absolute()) {
        LOG("The path is not absolute, changing it")
        jsonPath = std::filesystem::current_path()/jsonPath;
    }
    LOG("getting json files current dir")
    LOG(jsonPath)
    AS_EM(std::filesystem::exists(jsonPath), true, "Please create the json file,the json file was not exist")
    LOG("getting project path")
    const auto projectPath = jsonPath.parent_path();
    LOG(projectPath)
    LOG("will use g++ compiler")
    LOG("checking g++ compiler version")
    int sys_ret = system("g++ --version");
    AS_EM(sys_ret,0,"get g++ --version failed")
    const auto buildDir = projectPath / "build";
    LOG("getting build dir")
    LOG(buildDir)
    LOG("parsing json file")
    ifstream jsonFileStream(jsonPath);
    Json::Value jsonValue;
    Json::Reader jsonReader;
    jsonReader.parse(jsonFileStream,jsonValue);
    AS_NM(jsonValue["files"], Json::nullValue, "cannot read the json value `files` array")
    string projectFilesBuildString;
    LOG("parsing files in json values")
    for(auto i:jsonValue["files"]){
        projectFilesBuildString = projectFilesBuildString + " " +getProtectPath(getAbsolutePath(projectPath, std::filesystem::path(i.asString())));
    }
    printValue(projectFilesBuildString)
    LOG("parsing g++ compiler args")
    string compileArgs;
    for(auto i:jsonValue["args"]){
        compileArgs = compileArgs + " "+i.asString();
    }
    printValue(compileArgs)
    if(std::filesystem::exists(buildDir)==false){
        LOG("creating building dir")
        std::filesystem::create_directory(buildDir);
    }
    LOG("getting run time out")
    int run_timeout = 0;
    if(jsonValue["run_timeout"]!=Json::nullValue){
        run_timeout = jsonValue["run_timeout"].asInt();
        printValue(run_timeout)
    }
    LOG("compiling")
    string compileCommands;
    const path exePath = (buildDir/"main").string()+(isWindows==true?".exe":"");
    printValue(exePath)
    compileCommands=compileCommands+"g++ "+"-o "+getProtectPath(exePath)+" "+projectFilesBuildString+" "+compileArgs;
    printValue(compileCommands)
    AS_EM(system(compileCommands.c_str()), 0, "compile the program failed")
    
    auto cpuNums = getCpuNums();
    printValue(cpuNums)

    if(jsonValue["tests"].size()==0){
        LOG("cannot find tests")
        return 0;
    }
    LOG("parsing tests")
    vector<Test> tests(jsonValue["tests"].size()+1);
    auto &jsonTests = jsonValue["tests"];
    for(unsigned int i=1;i<=jsonTests.size();i++){
        tests[i]={
            .id = i,
            .in=getAbsolutePath(projectPath, getValueOrPanic(jsonTests[i-1]["in"]).asString()),
            .ans=getAbsolutePath(projectPath,getValueOrPanic(jsonTests[i-1]["ans"]).asString()),
        };
    }
    queue<std::function<ProgramRet()>> tasks;
    const auto testsPath = projectPath/"tests";
    if(!exists(testsPath)){
        LOG("creating the test dir")
        std::filesystem::create_directory(testsPath);
    }
    printValue(testsPath)
    const string exePathStr = getProtectPath(exePath);
    for (int j=1;j<=jsonTests.size();j++) {
    auto &i = tests[j];
    tasks.push([i, &exePathStr, &testsPath]() -> ProgramRet {
            string id_str = std::to_string(i.id);
            path outPath = testsPath / (id_str + ".out");

            // 构建命令
            string commands = exePathStr + " < " + getProtectPath(i.in) + " > " + getProtectPath(outPath);

            // 输出构建的命令进行调试
            LOG(commands);

            // 执行命令
            auto start = high_resolution_clock::now();
            int result = system(commands.c_str());
            auto end = high_resolution_clock::now();

            if (result != 0) {
                cerr << "Error executing command: " << commands << endl;
                exit(1);
            }

            return ProgramRet{
                .start = start,
                .end = end,
                .outPath=outPath,
                .id=i.id
            };
        });
    }
    vector<std::future<ProgramRet>> futures;
    while(tasks.size()>0){
        auto i = tasks.front();
        tasks.pop();
        futures.push_back(std::async(std::launch::async,i));
    }
    vector<ProgramRet> results(jsonTests.size()+1);
    for(auto &i:futures){
        if(run_timeout>0){
            if(i.wait_for(std::chrono::milliseconds(run_timeout))==std::future_status::ready){
                auto ret = i.get();
                results[ret.id]=ret;
            }
        }else{
            i.wait();
            auto ret = i.get();
            results[ret.id]=ret;
        }
    }
    LOG("get results done")
    for(int i=1;i<=jsonTests.size();i++){
        LOG(results[i])
    }

    vector<Statu> status(jsonTests.size()+1);
    auto a = std::chrono::duration_cast<std::chrono::milliseconds>(results[0].end-results[0].start);
    
    for(int i=1;i<=jsonTests.size();i++){
        printValue(tests[i].ans)
        status[i]={
            .isTLE=(results[i].id==0?true:false),
            .cmp=(results[i].id==0?0:compareFiles(results[i].outPath,tests[i].ans)),
            .id=i,
            .spendTime = std::chrono::duration_cast<std::chrono::milliseconds>(results[i].end-results[i].start),
        };
    }
    cout<<endl<<"----------results---------"<<endl;
    for(int i=1;i<=jsonTests.size();i++){
        cout<<status[i]<<endl;
    }
    exit(0);
}

void trimTrailingWhitespace(std::string& str) {
    str.erase(std::find_if(str.rbegin(), str.rend(), [](unsigned char ch) {
        return !std::isspace(ch);
    }).base(), str.end());
}

int compareFiles(const path &path1, const path &path2) {
    std::ifstream file1(path1);
    std::ifstream file2(path2);

    if (!file1.is_open()) {
        std::cerr << "cannot open the file at: " << path1 << std::endl;
        exit(-1);
    }
    if (!file2.is_open()) {
        std::cerr << "cannot open the file at: " << path2 << std::endl;
        exit(-1);
    }

    std::string line1, line2;
    int lineNumber = 1;

    while (std::getline(file1, line1) && std::getline(file2, line2)) {
        trimTrailingWhitespace(line1);
        trimTrailingWhitespace(line2);

        if (line1 != line2) {
            return lineNumber;  // 返回第一个不同的行号
        }
        ++lineNumber;
    }

    // 如果一个文件还没读完
    if (std::getline(file1, line1) || std::getline(file2, line2)) {
        return lineNumber;
    }

    return 0;  // 文件内容完全一致
}
string getProtectPath(const std::filesystem::path &p){
    return "\""+p.string()+"\"";
}

template<typename T>
T getValueOrPanic(T value){
    AS_NE(value, Json::nullValue)
    return value;
}

std::filesystem::path getAbsolutePath(std::filesystem::path rootPath,std::filesystem::path filePath){
    if(filePath.is_absolute()==false){
        filePath = rootPath/filePath;
    }
    return filePath;
}